﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.Xml;

namespace DiskGarden
{
	[Serializable]
	public class DirectoryNode : FileNode
	{
		[NonSerialized]
		[XmlArrayItem("file")]
		public List<FileNode> Files;

		[NonSerialized]
		[XmlArrayItem("dir")]
		public List<DirectoryNode> Dirs;

		public DirectoryNode()
		{
			this.type = NodeType.Directory;
			Files = new List<FileNode>();
			Dirs = new List<DirectoryNode>();
		}

		protected bool CheckAttributes(FileAttributes attributes)
		{
			return (Options.AllowHiddenFiles || FileAttributes.Hidden != (attributes & FileAttributes.Hidden))
				&& (Options.AllowSystemFiles || FileAttributes.System != (attributes & FileAttributes.System));
		}


		public void AddFile(FileInfo F)
		{
			// ta metoda ma byc wywolywana posrednio przez TDir.AddDir()
			// dodaje pojedynczy plik.

			if (CheckAttributes(F.Attributes))
			{
				FileNode N = new FileNode();
				N.Parent = this;
				N.name = F.Name;
				N.Path = F.DirectoryName;
				N.Size = F.Length;
				N.Date = F.CreationTime;
				N.Attributes = F.Attributes;

				this.Files.Add(N);
				Root.InvokeNodeAdded(N);
			}
		}

		public void AddDir(DirectoryInfo D)
		{
			try
			{
				// ta metoda ma byc wywolywana posrednio przez TRoot.AddDir(),
				// poniewaz dodaje rekurencyjnie katalogi po klasie DirectoryInfo,
				// a nie po sciezce katalogu.

				// Tworzy obiekt N klasy TDir.

				if (CheckAttributes(D.Attributes))
				{
					DirectoryNode N = new DirectoryNode();
					N.Parent = this;
					N.name = D.Name;
					N.Path = D.FullName;
					N.Date = D.CreationTime;
					N.Attributes = D.Attributes;
					this.Dirs.Add(N);
					Root.InvokeNodeAdded(N);

					// katalogowanie plikow

					FileInfo[] TabF = D.GetFiles();
					foreach (FileInfo cFile in TabF)
						N.AddFile(cFile);

					// katalogowanie podkatalogow
					DirectoryInfo[] TabD = D.GetDirectories();
					foreach (DirectoryInfo cDir in TabD)
						N.AddDir(cDir);
				}
			}
			catch (UnauthorizedAccessException) { }
		}

		public long GetSize()
		{
			// zapisuje do pola Size wielkosc calego katalogu (z plikami i podkatalogami),
			// równiez podkatalogi beda mialy zapisana wielkosc w swoim Size,
			// wiec ta metode wystarczy wywolac tylko raz z klasy root

			// zlicza rekurencynie wielkości wszystkich plików i folderów
			this.Size = 0;
			foreach (FileNode obj in this.Files)
				this.Size += obj.Size;

			foreach (DirectoryNode obj in this.Dirs)
				this.Size += obj.GetSize();

			return this.Size;
		}

		protected override void UpdateTree(TreeNodeCollection treeNodes)
		{
			// uaktualnia pojedynczy podkatalog
			UpdateTree(treeNodes, Dirs as IList);
		}

		// odswieza widok na ListView
		public override void UpdateFileList(ListView listView)
		{
			listView.BeginUpdate();
			listView.Items.Clear();
			UpdateFileList(listView, Dirs as IList);
			UpdateFileList(listView, Files as IList);
			listView.EndUpdate();
		}

		// sortuje pliki i podkatalogi
		public override void Sort()
		{
			this.Files.Sort();
			this.Dirs.Sort();
		}

		public int CompareTo(DirectoryNode D)
		{
			return this.Name.CompareTo(D.Name);
		}

		public void Search(string val, List<FileNode> results)
		{
			foreach (FileNode n in this.Files)
			{
				if (n.Name.ToLower().Contains(val))
				{
					results.Add(n);
				}
			}
			foreach (DirectoryNode n in this.Dirs)
			{
				if (n.Name.ToLower().Contains(val))
				{
					results.Add(n);
				}
				n.Search(val, results);
			}
		}

		protected override void RebuildParents()
		{
			RebuildParents(Files as IList);
			RebuildParents(Dirs as IList);
		}

		public override void RemoveChild(Node node)
		{
			bool deleted = false;
			if (node.Type == NodeType.Directory)
				deleted = Dirs.Remove(node as DirectoryNode);
			else if (node.Type == NodeType.File)
				deleted = Files.Remove(node as FileNode);

			if (deleted)
				Root.InvokeNodeDeleted(node);
		}

		internal override void WriteXml(System.Xml.XmlWriter xml)
		{
			base.WriteXmlAttributes(xml);
			base.WriteXmlTags(xml);
			if (Files != null && Files.Count > 0)
			{
				xml.WriteStartElement("files");
				for (int i = 0; i < Files.Count; i++)
				{
					xml.WriteStartElement("file");
					Files[i].WriteXml(xml);
					xml.WriteEndElement();
				}
				xml.WriteEndElement();
			}

			if (Dirs != null && Dirs.Count > 0)
			{
				xml.WriteStartElement("dirs");
				for (int i = 0; i < Dirs.Count; i++)
				{
					xml.WriteStartElement("dir");
					Dirs[i].WriteXml(xml);
					xml.WriteEndElement();
				}
				xml.WriteEndElement();
			}
		}

		internal override void ReadXml(System.Xml.XmlReader xml)
		{
			base.ReadXml(xml);
			if (xml.IsEmptyElement) return;
			string nodeName = xml.Name;
			while (xml.Read())
			{
				if (xml.NodeType == XmlNodeType.EndElement && xml.Name == nodeName)
					return;
				if (xml.NodeType == XmlNodeType.Element)
				{
					switch (xml.Name)
					{
						case "files":
							while (xml.Read())
							{
								if (xml.NodeType == XmlNodeType.EndElement && xml.Name == "files")
									break;
								if (xml.Name == "file")
								{
									FileNode node = new FileNode();
									node.ReadXml(xml);
									Files.Add(node);
								}
							}
							break;
						case "dirs":
							while (xml.Read())
							{
								if (xml.NodeType == XmlNodeType.EndElement && xml.Name == "dirs")
									break;
								if (xml.Name == "dir")
								{
									DirectoryNode node = new DirectoryNode();
									node.ReadXml(xml);
									Dirs.Add(node);
								}
							}
							break;
						case "tags": 
							ReadXmlTags(xml);
							break;
					}

				}
			}
		}
	}
}
